// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "content/shell/renderer/layout_test/leak_detector.h"

#include "base/json/json_writer.h"
#include "base/logging.h"
#include "base/values.h"
#include "content/shell/renderer/layout_test/blink_test_runner.h"
#include "third_party/WebKit/public/web/WebLeakDetector.h"

using blink::WebLeakDetector;

namespace content {

// The initial states of the DOM objects at about:blank. The four nodes are a
// Document, a HTML, a HEAD and a BODY.
//
// TODO(hajimehoshi): Now these are hard-corded. If we add a target to count
// objects like RefCounted whose initial state is diffcult to estimate, we stop
// using hard-coded values. Instead, we need to load about:blank ahead of the
// layout tests actually and initialize LeakDetector by the got values.
const int kInitialNumberOfLiveAudioNodes = 0;
const int kInitialNumberOfLiveDocuments = 1;
const int kInitialNumberOfLiveNodes = 4;
const int kInitialNumberOfLiveLayoutObjects = 3;
const int kInitialNumberOfLiveResources = 0;
const int kInitialNumberOfScriptPromises = 0;
const int kInitialNumberOfLiveFrames = 1;
const int kInitialNumberOfWorkerGlobalScopes = 0;

// In the initial state, there are two SuspendableObjects (FontFaceSet created
// by HTMLDocument and SuspendableTimer created by DocumentLoader).
const int kInitialNumberOfLiveSuspendableObject = 2;

// This includes not only about:blank's context but also ScriptRegexp (e.g.
// created by isValidEmailAddress in EmailInputType.cpp). The leak detector
// always creates the latter to stabilize the number of V8PerContextData
// objects.
const int kInitialNumberOfV8PerContextData = 2;

LeakDetector::LeakDetector(BlinkTestRunner* test_runner)
    : test_runner_(test_runner)
    , web_leak_detector_(blink::WebLeakDetector::create(this))
{
    previous_result_.numberOfLiveAudioNodes = kInitialNumberOfLiveAudioNodes;
    previous_result_.numberOfLiveDocuments = kInitialNumberOfLiveDocuments;
    previous_result_.numberOfLiveNodes = kInitialNumberOfLiveNodes;
    previous_result_.numberOfLiveLayoutObjects = kInitialNumberOfLiveLayoutObjects;
    previous_result_.numberOfLiveResources = kInitialNumberOfLiveResources;
    previous_result_.numberOfLiveSuspendableObjects = kInitialNumberOfLiveSuspendableObject;
    previous_result_.numberOfLiveScriptPromises = kInitialNumberOfScriptPromises;
    previous_result_.numberOfLiveFrames = kInitialNumberOfLiveFrames;
    previous_result_.numberOfLiveV8PerContextData = kInitialNumberOfV8PerContextData;
    previous_result_.numberOfWorkerGlobalScopes = kInitialNumberOfWorkerGlobalScopes;
}

LeakDetector::~LeakDetector()
{
}

void LeakDetector::TryLeakDetection(blink::WebFrame* frame)
{
    web_leak_detector_->prepareForLeakDetection(frame);
    web_leak_detector_->collectGarbageAndReport();
}

void LeakDetector::onLeakDetectionComplete(
    const WebLeakDetectorClient::Result& result)
{
    LeakDetectionResult report;
    report.leaked = false;
    base::DictionaryValue detail;

    if (previous_result_.numberOfLiveAudioNodes < result.numberOfLiveAudioNodes) {
        base::ListValue* list = new base::ListValue();
        list->AppendInteger(previous_result_.numberOfLiveAudioNodes);
        list->AppendInteger(result.numberOfLiveAudioNodes);
        detail.Set("numberOfLiveAudioNodes", list);
    }
    if (previous_result_.numberOfLiveDocuments < result.numberOfLiveDocuments) {
        base::ListValue* list = new base::ListValue();
        list->AppendInteger(previous_result_.numberOfLiveDocuments);
        list->AppendInteger(result.numberOfLiveDocuments);
        detail.Set("numberOfLiveDocuments", list);
    }
    if (previous_result_.numberOfLiveNodes < result.numberOfLiveNodes) {
        base::ListValue* list = new base::ListValue();
        list->AppendInteger(previous_result_.numberOfLiveNodes);
        list->AppendInteger(result.numberOfLiveNodes);
        detail.Set("numberOfLiveNodes", list);
    }
    if (previous_result_.numberOfLiveLayoutObjects < result.numberOfLiveLayoutObjects) {
        base::ListValue* list = new base::ListValue();
        list->AppendInteger(previous_result_.numberOfLiveLayoutObjects);
        list->AppendInteger(result.numberOfLiveLayoutObjects);
        detail.Set("numberOfLiveLayoutObjects", list);
    }
    if (previous_result_.numberOfLiveResources < result.numberOfLiveResources) {
        base::ListValue* list = new base::ListValue();
        list->AppendInteger(previous_result_.numberOfLiveResources);
        list->AppendInteger(result.numberOfLiveResources);
        detail.Set("numberOfLiveResources", list);
    }
    if (previous_result_.numberOfLiveSuspendableObjects < result.numberOfLiveSuspendableObjects) {
        base::ListValue* list = new base::ListValue();
        list->AppendInteger(previous_result_.numberOfLiveSuspendableObjects);
        list->AppendInteger(result.numberOfLiveSuspendableObjects);
        detail.Set("numberOfLiveSuspendableObjects", list);
    }
    if (previous_result_.numberOfLiveScriptPromises < result.numberOfLiveScriptPromises) {
        base::ListValue* list = new base::ListValue();
        list->AppendInteger(previous_result_.numberOfLiveScriptPromises);
        list->AppendInteger(result.numberOfLiveScriptPromises);
        detail.Set("numberOfLiveScriptPromises", list);
    }
    if (previous_result_.numberOfLiveFrames < result.numberOfLiveFrames) {
        base::ListValue* list = new base::ListValue();
        list->AppendInteger(previous_result_.numberOfLiveFrames);
        list->AppendInteger(result.numberOfLiveFrames);
        detail.Set("numberOfLiveFrames", list);
    }
    if (previous_result_.numberOfLiveV8PerContextData < result.numberOfLiveV8PerContextData) {
        base::ListValue* list = new base::ListValue();
        list->AppendInteger(previous_result_.numberOfLiveV8PerContextData);
        list->AppendInteger(result.numberOfLiveV8PerContextData);
        detail.Set("numberOfLiveV8PerContextData", list);
    }
    if (previous_result_.numberOfWorkerGlobalScopes < result.numberOfWorkerGlobalScopes) {
        base::ListValue* list = new base::ListValue();
        list->AppendInteger(previous_result_.numberOfWorkerGlobalScopes);
        list->AppendInteger(result.numberOfWorkerGlobalScopes);
        detail.Set("numberOfWorkerGlobalScopes", list);
    }

    if (!detail.empty()) {
        std::string detail_str;
        base::JSONWriter::Write(detail, &detail_str);
        report.detail = detail_str;
        report.leaked = true;
    }

    previous_result_ = result;
    test_runner_->ReportLeakDetectionResult(report);
}

} // namespace content
